﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DonutLandLibrary.Data;

namespace DonutLandLibrary.Robots.GeneticAlgorithm
{
    public class GeneticProfile
    {
        private const int energyDepletionRateMax = 8;
        private const int energyDepletionRateMin = 1;
        private const int maximumEnergyLevelMax = 8;
        private const int maximumEnergyDepletionReserveMax = 50;
        private const int protectiveCasingRepairRateMax = 8;
        private const int protectiveCasingRepairRateMin = 1;
        private const int maximumProtectiveCasingMax = 8;
        private const int protectiveCasingRepairReserveMax = 50;

        private int maximumEnergyLevel;
        private int maximumProtectiveCasing;
        private int energyDepletionRate;
        private int energyDepletionReserve;
        private int protectiveCasingRepairRate;
        private int protectiveCasingRepairReserve;

        public int ID { get; set; }

        public int MaximumEnergyLevel
        {
            get
            {
                return maximumEnergyLevel;
            }
        }

        public int MaximumProtectiveCasing
        {
            get
            {
                return maximumProtectiveCasing;
            }
        }

        public int EnergyDepletionRate
        {
            get
            {
                return energyDepletionRate;
            }
        }

        public int EnergyDepletionReserve
        {
            get
            {
                return energyDepletionReserve;
            }
        }

        public int ProtectiveCasingRepairRate
        {
            get
            {
                return protectiveCasingRepairRate;
            }
        }

        public int ProtectiveCasingRepairReserve
        {
            get
            {
                return protectiveCasingRepairRate;
            }
        }

        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();

            builder.Append(Convert.ToString(maximumEnergyLevel, 2).PadLeft(4,'0'));
            builder.Append(Convert.ToString(maximumProtectiveCasing, 2).PadLeft(4,'0'));
            builder.Append(Convert.ToString(energyDepletionRate, 2).PadLeft(4,'0'));
            builder.Append(Convert.ToString(energyDepletionReserve, 2).PadLeft(6,'0'));
            builder.Append(Convert.ToString(protectiveCasingRepairRate, 2).PadLeft(4,'0'));
            builder.Append(Convert.ToString(protectiveCasingRepairReserve, 2).PadLeft(6,'0'));

            return builder.ToString();
        }

        public GeneticProfile(int maxEnergyLevel,
                              int maxProtectiveCasing,
                              int eneDepletionRate,
                              int eneDepletionReserve,
                              int proCasingRepairRate,
                              int proCasingRepairReserve)
        {
            maximumEnergyLevel = maxEnergyLevel;
            maximumProtectiveCasing = maxProtectiveCasing;
            energyDepletionRate = eneDepletionRate;
            energyDepletionReserve = eneDepletionReserve;
            protectiveCasingRepairRate = proCasingRepairRate;
            protectiveCasingRepairReserve = proCasingRepairReserve;
        }

        public GeneticProfile(string binaryStream)
        {
            int maxEnergyLevel;
            int maxProtectiveCasing;
            int eneDepletionRate;
            int eneDepletionReserve;
            int proCasingRepairRate;
            int proCasingRepairReserve;

            maxEnergyLevel = Convert.ToInt32(binaryStream.Substring(0, 4), 2);
            maxProtectiveCasing = Convert.ToInt32(binaryStream.Substring(4, 4), 2);
            eneDepletionRate = Convert.ToInt32(binaryStream.Substring(8, 4), 2);
            eneDepletionReserve = Convert.ToInt32(binaryStream.Substring(12, 6), 2);
            proCasingRepairRate = Convert.ToInt32(binaryStream.Substring(18, 4), 2);
            proCasingRepairReserve = Convert.ToInt32(binaryStream.Substring(22, 6), 2);

            maximumEnergyLevel = maxEnergyLevel;
            maximumProtectiveCasing = maxProtectiveCasing;
            energyDepletionRate = eneDepletionRate;
            energyDepletionReserve = eneDepletionReserve;
            protectiveCasingRepairRate = proCasingRepairRate;
            protectiveCasingRepairReserve = proCasingRepairReserve;
        }

        public GeneticProfile()
        {
        }

        public GeneticProfile(int identity)
        {
            DonutLandLibrary.GeneticProfile geneProfile = GeneticProfilesData.GetGeneticProfile(identity);

            ID = geneProfile.ID;
            energyDepletionRate = geneProfile.EnergyDepletionRate;
            energyDepletionReserve = geneProfile.MaximumEnergyDepletionReserve;
            maximumEnergyLevel = geneProfile.MaximumEnergyLevel;
            maximumProtectiveCasing = geneProfile.MaximumProtectiveCasing;
            protectiveCasingRepairRate = geneProfile.ProtectiveCasingRepairRate;
            protectiveCasingRepairReserve = geneProfile.ProtectiveCasingRepairReserve;
        }

        public string Reproduce(GeneticProfile mateGeneticProfile)
        {
            int CrossOverProbability = 50;
            int MutationProbability = 50;
            string geneProfileString = this.ToString();

            RandomGenerator randomGenerator = RandomGenerator.getInstance();

            //Does cross over happen?
            if (randomGenerator.RandomInt(0, 100) < CrossOverProbability)
            {
                geneProfileString = CrossOver(geneProfileString, mateGeneticProfile.ToString());
            }

            //Does mutation happen?
            if (randomGenerator.RandomInt(0, 100) < MutationProbability)
            {
                geneProfileString = Mutate(geneProfileString);
            }

            return geneProfileString;
        }

        private string CrossOver(String geneProfile, String mateGeneticProfile)
        {
            RandomGenerator randomGenerator = RandomGenerator.getInstance();
            int crossOverIndex;

            crossOverIndex = randomGenerator.RandomInt(0, geneProfile.Count() - 2);

            geneProfile = geneProfile.Substring(0, crossOverIndex) + mateGeneticProfile.Substring(crossOverIndex);
            
            return geneProfile;
        }

        private string Mutate(String geneProfile)
        {
            RandomGenerator randomGenerator = RandomGenerator.getInstance();
            int mutationIndex;
            char[] geneCharArray = geneProfile.ToCharArray();

            mutationIndex = randomGenerator.RandomInt(0, geneProfile.Count() - 1);

            if (geneCharArray[mutationIndex] == 0)
            {
                geneCharArray[mutationIndex] = '1';
            }
            else if (geneCharArray[mutationIndex] == 1)
            {
                geneCharArray[mutationIndex] = '0';
            }

            geneProfile = new String(geneCharArray);

            return geneProfile;
        }

        public static Robot SelectRobotByRoulette(List<Robot> robotCollection)
        {
            int totalRobotCollectionFitness = 0;
            RandomGenerator randomGenerator = RandomGenerator.getInstance();
            int cumulativeRoulettePercentage = 0;
            int roulletePercentage = randomGenerator.RandomInt(0, 100);
            int previousRoulettePercentage;
            Robot selectedRobot = null;

            foreach (Robot robot in robotCollection)
            {
                totalRobotCollectionFitness += robot.Fitness;
            }

            foreach (Robot robot in robotCollection)
            {
                previousRoulettePercentage = cumulativeRoulettePercentage;
                cumulativeRoulettePercentage += (int)(((double)robot.Fitness / (double)totalRobotCollectionFitness) * 100);

                if (
                    (roulletePercentage > previousRoulettePercentage) &&
                    (roulletePercentage <= cumulativeRoulettePercentage))
                {
                    selectedRobot = robot;

                    break;
                }
            }

            if (selectedRobot == null)
            {
                selectedRobot = robotCollection.Last();
            }

            return selectedRobot;
        }
    }
}
